package application

import (
	"encoding/json"
	"fmt"
	"github.com/shopspring/decimal"
	"github.com/yejingxuan/accumulate/domain/entity"
	"github.com/yejingxuan/accumulate/domain/repository"
	"github.com/yejingxuan/accumulate/infrastructure/analyse"
	"github.com/yejingxuan/accumulate/infrastructure/crawler"
	"github.com/yejingxuan/accumulate/infrastructure/logger"
	"github.com/yejingxuan/accumulate/infrastructure/utils"
	"github.com/yejingxuan/accumulate/interface/dto"
	"go.uber.org/zap"
	"strconv"
	"sync"
	"time"
)

type StockAppInterface interface {
	QueryStockPage(req *dto.QueryStockReq) ([]entity.StockAllInfo, int, error) //查询stock数据
	Follow(req *dto.FollowStockReq) error                                      //关注stock
	GetStockInfoByCode(code string) (*entity.StockAllInfo, error)              //获取stock详细信息
	Comment(req *dto.CommentStockReq) error                                    //评论stock

	UpdateStock() error    //更新stock基本数据
	UpdateKline() error    //更新全部kline数据
	KlineProcess() float64 //更新kline数据的进度
	AnalyseInfo() error    //数据执行分析

}

//StockAppInterface实现
type stockApp struct {
	stockRepo  repository.StockRepo
	klineRepo  repository.KlineRepo
	crawler    crawler.StockCrawlerInterface
	aiAnalyzer analyse.AIAnalyzerInterface
}

//构造函数
func NewStockApp(repo repository.StockRepo, klineRepo repository.KlineRepo,
	crawler crawler.StockCrawlerInterface, aiAnalyzer analyse.AIAnalyzerInterface) *stockApp {
	return &stockApp{stockRepo: repo, klineRepo: klineRepo, crawler: crawler, aiAnalyzer: aiAnalyzer}
}

//查询stock数据
func (s stockApp) QueryStockPage(req *dto.QueryStockReq) ([]entity.StockAllInfo, int, error) {
	data, total, err := s.stockRepo.GetStockPageInfo(req.Name, req.Symbol, req.Tags, req.Status, req.Type, req.PageSize, req.PageNo, req.SortField, req.SortOrder)
	return data, total, err
}

//关注stock
func (s stockApp) Follow(req *dto.FollowStockReq) error {
	info, _ := s.stockRepo.GetStockInfoByCode(req.Symbol)
	if info.ReadStatus == req.Status {
		res, _ := time.Parse("2006-01-02", "0001-01-01")
		return s.stockRepo.Follow(req.Symbol, info.ServiceSymbol, 0, "", res)
	}
	return s.stockRepo.Follow(req.Symbol, info.ServiceSymbol, info.Current, req.Status, time.Now())
}

//获取stock详细信息
func (s stockApp) GetStockInfoByCode(code string) (*entity.StockAllInfo, error) {
	info, err := s.stockRepo.GetStockInfoByCode(code)
	return info, err
}

//评论stock
func (s stockApp) Comment(req *dto.CommentStockReq) error {
	return s.stockRepo.Comment(req.Symbol, req.Comment)
}

//更新stock基本数据
func (s stockApp) UpdateStock() error {
	/*	err := s.stockRepo.DelAllStock()
		if err != nil {
			return err
		}*/
	s.crawler.UpdateAllStockBaseData()
	return nil
}

//更新全部kline数据
func (s stockApp) UpdateKline() error {
	//清空kline
	err := s.klineRepo.DelAllKline()
	if err != nil {
		return err
	}

	pageSize := 500
	pageNo := 1
	_, total, err := s.stockRepo.GetStockPageInfo("", "", nil, "", nil, 10, pageNo, "", "")
	pageCount := total/pageSize + 1

	for i := pageNo; i <= pageCount; i++ {
		data, _, _ := s.stockRepo.GetStockPageInfo("", "", nil, "", nil, pageSize, i, "", "")
		for _, info := range data {
			time.Sleep(time.Duration(utils.RandInt64(60, 120)) * time.Millisecond)
			err = s.crawler.UpdateKLineDataByCode(info.Symbol)
			if err != nil {
				return err
			}
			logger.Info("update kline data", zap.Any("name", info.Name))
		}
	}
	return err
}

//更新kline数据的进度
func (s stockApp) KlineProcess() float64 {
	_, stockTotal, _ := s.stockRepo.GetStockPageInfo("", "", nil, "", nil, 10, 1, "", "")
	_, klineTotal, _ := s.klineRepo.GetKlinePageInfo(10, 1)

	res := decimal.NewFromInt32(int32(klineTotal)).Div(decimal.NewFromInt32(int32(stockTotal)))
	f, _ := res.Float64()

	value, _ := strconv.ParseFloat(fmt.Sprintf("%.2f", f*100), 64)
	return value
}

//数据执行分析
func (s stockApp) AnalyseInfo() error {
	pageSize := 100
	pageNo := 1
	_, total, err := s.klineRepo.GetKlinePageInfo(pageSize, pageNo)
	pageCount := total/pageSize + 1
	wg := sync.WaitGroup{}
	wg.Add(pageCount)
	for i := pageNo; i <= pageCount; i++ {
		info, _, _ := s.klineRepo.GetKlinePageInfo(pageSize, i)
		go s.batchAnalyse(info, &wg)
	}
	wg.Wait()
	return err
}

func (s stockApp) batchAnalyse(info []entity.Kline, wg *sync.WaitGroup) {
	for _, kline := range info {
		resp := dto.KlineDataResp{}
		_ = json.Unmarshal([]byte(kline.Kline), &resp.Data)
		//数据清洗
		data := s.aiAnalyzer.DataClean(resp)
		jsonData, _ := json.Marshal(data)
		_ = s.stockRepo.UpdateStockRecentKine(resp.Data.Symbol, string(jsonData))
		logger.Info("analyse data:", zap.Any("symbol", resp.Data.Symbol))
		//标签计算
		tags := s.aiAnalyzer.CalculateTag(data)
		tagsStr := ""
		for _, tag := range tags {
			tagsStr += tag + ","
		}
		if len(tags) > 0 {
			tagsStr = tagsStr[:len(tagsStr)-1]
		}
		_ = s.stockRepo.UpdateStockTags(resp.Data.Symbol, tagsStr)
	}
	wg.Done()
}
